package com.tyron.builder.compiler.dex;

import android.util.Log;

import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.DiagnosticsLevel;
import com.android.tools.r8.errors.DuplicateTypesDiagnostic;
import com.android.tools.r8.origin.Origin;
import com.tyron.builder.log.ILogger;
import com.tyron.builder.model.CodeAssistLibrary;
import com.tyron.builder.model.DiagnosticWrapper;
import com.tyron.builder.project.api.JavaModule;

import java.io.File;
import java.util.Collection;

public class DexDiagnosticHandler implements DiagnosticsHandler {

    private static final String METAINF_ERROR = "Resource 'META-INF/MANIFEST.MF' already exists.";
    private static final String LIBRARY_DIR = "build/libs/";
    private static final String CLASSES_DIR = "build/intermediate/classes/";

    private final ILogger mLogger;
    private final JavaModule mModule;

    public DexDiagnosticHandler(ILogger logger, JavaModule module) {
        mLogger = logger;
        mModule = module;
    }

    @Override
    public void error(Diagnostic diagnostic) {
        mLogger.error(wrap(diagnostic, DiagnosticsLevel.ERROR));
    }

    @Override
    public void warning(Diagnostic diagnostic) {
        mLogger.warning(wrap(diagnostic, DiagnosticsLevel.WARNING));
    }

    @Override
    public void info(Diagnostic diagnostic) {
        mLogger.info(wrap(diagnostic, DiagnosticsLevel.INFO));
    }

    @Override
    public DiagnosticsLevel modifyDiagnosticsLevel(DiagnosticsLevel diagnosticsLevel,
                                                   Diagnostic diagnostic) {
        if (diagnostic.getDiagnosticMessage().equals(METAINF_ERROR)) {
            return DiagnosticsLevel.WARNING;
        }

        Log.d("DiagnosticHandler", diagnostic.getDiagnosticMessage());
        return diagnosticsLevel;
    }

    private DiagnosticWrapper wrap(Diagnostic diagnostic, DiagnosticsLevel level) {
        DiagnosticWrapper wrapper = new DiagnosticWrapper();
        if (diagnostic instanceof DuplicateTypesDiagnostic) {
            DuplicateTypesDiagnostic d = (DuplicateTypesDiagnostic) diagnostic;

            String typeName = d.getType().getTypeName();

            String message = "" +
                    "The type " + typeName + " is defined multiple times in multiple jars. " +
                    "There can only be one class with the same package and name. Please locate the " +
                    "following jars and determine which class is appropriate to keep. \n\n";
            message += "Files: \n";
            message += printDuplicateOrigins(d.getOrigins());
            wrapper.setMessage(message);
        } else {
            wrapper.setMessage(diagnostic.getDiagnosticMessage());
        }
        switch (level) {
            case WARNING:
                wrapper.setKind(javax.tools.Diagnostic.Kind.WARNING);
                break;
            case ERROR:
                wrapper.setKind(javax.tools.Diagnostic.Kind.ERROR);
                break;
            case INFO:
                wrapper.setKind(javax.tools.Diagnostic.Kind.NOTE);
                break;
        }
        return wrapper;
    }

    private String printDuplicateOrigins(Collection<Origin> origins) {
        StringBuilder builder = new StringBuilder();

        for (Origin origin : origins) {
            builder.append("\n");
            if (isClassFile(origin)) {
                builder.append("Class/Source file");
                builder.append("\n");
                builder.append("path: ");
                builder.append(getClassFile(origin));
            } else if (isJarFile(origin)) {
                builder.append("Jar file");
                builder.append("\n");
                builder.append("path: ");
                builder.append(getLibraryFile(origin));
            } else {
                builder.append("path: ");
                builder.append(origin.part());
            }
            builder.append("\n");
        }
        return builder.toString();
    }

    private boolean isJarFile(Origin origin) {
        return origin.part().contains(LIBRARY_DIR);
    }

    private String getLibraryFile(Origin origin) {
        String part = origin.part();
        File file = new File(part);
        if (!file.exists()) {
            return part;
        }

        File parent = file.getParentFile();
        if (parent == null) {
            return part;
        }

        String hash = parent.getName();
        CodeAssistLibrary library = mModule.getLibrary(hash);
        if (library == null) {
            return part;
        }

        if (library.isDependency()) {
            return library.getDeclaration();
        }

        return library.getSourceFile().getAbsolutePath();
    }

    private boolean isClassFile(Origin origin) {
        return origin.part().contains(CLASSES_DIR);
    }

    private String getClassFile(Origin origin) {
        String part = origin.part();
        int index = part.indexOf(CLASSES_DIR);
        if (index == -1) {
            return part;
        }
        int endIndex = part.length();
        if (part.endsWith(".dex")) {
            endIndex -= ".dex".length();
        }
        index += CLASSES_DIR.length();
        String fqn = part.substring(index, endIndex)
                .replace('/', '.');

        File javaFile = mModule.getJavaFile(fqn);
        if (javaFile == null) {
            return part;
        }

        return javaFile.getAbsolutePath();
    }
}